﻿namespace Mercury.ParticleEngine
{
    using System;
    using System.Diagnostics.Contracts;
    using System.Runtime.InteropServices;
    using System.Runtime.Serialization;

    /// <summary>
    /// An immutable data structure encapsulating a rotation using axis-angle representation.
    /// </summary>
    /// <seealso>http://en.wikipedia.org/wiki/Axis_angle</seealso>
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct AxisAngle : ISerializable,
                              IEquatable<AxisAngle>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="AxisAngle"/> struct.
        /// </summary>
        /// <param name="axis">The axis of rotation.</param>
        /// <param name="angle">The angle of rotation around the axis.</param>
        public AxisAngle(Axis axis, RadianAngle angle)
        {
            _axis = axis;
            _angle = angle;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AxisAngle"/> struct.
        /// </summary>
        /// <param name="info">A serialization info instance.</param>
        /// <param name="context">A reference to the streaming context.</param>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if the value passed to the <paramref name="info"/> parameter is <c>null</c>.
        /// </exception>
        internal AxisAngle(SerializationInfo info, StreamingContext context)
        {
            Contract.Requires<ArgumentNullException>(info != null);

            _axis = (Axis)info.GetValue("axis", typeof(Axis));
            _angle = (RadianAngle)info.GetValue("angle", typeof(RadianAngle));
        }

        private readonly Axis _axis;
        private readonly RadianAngle _angle;

        /// <summary>
        /// Gets the axis of rotation.
        /// </summary>
        public Axis Axis
        {
            get { return _axis; }
        }

        /// <summary>
        /// Gets the angle of rotation around the axis.
        /// </summary>
        public RadianAngle Angle
        {
            get { return _angle; }
        }

        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <returns>
        /// <c>true</c> if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, <c>false</c>.
        /// </returns>
        /// <param name="obj">Another object to compare to.</param>
        public override Boolean Equals(Object obj)
        {
            if (ReferenceEquals(null, obj))
                return false;
            
            return obj is AxisAngle && this.Equals((AxisAngle)obj);
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public Boolean Equals(AxisAngle other)
        {
            return _axis.Equals(other._axis) && _angle.Equals(other._angle);
        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        public override Int32 GetHashCode()
        {
            unchecked
            {
                var hashCode = _axis.GetHashCode();
                
                hashCode = (hashCode * 397) ^ _angle.GetHashCode();
                
                return hashCode;
            }
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override String ToString()
        {
            return String.Format("([{0:x}, {0:y}, {0:z}], {1})", _axis, _angle);
        }

        /// <summary>
        /// Populates a <see cref="System.Runtime.Serialization.SerializationInfo"/> with the data
        /// needed to serialize the target object.
        /// </summary>
        /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/>
        /// to populate with data.</param>
        /// <param name="context">The destination (see System.Runtime.Serialization.StreamingContext)
        /// for this serialization.</param>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if the value passed to the <paramref name="info"/> parameter is <c>null</c>.
        /// </exception>
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("axis", _axis, typeof(Axis));
            info.AddValue("angle", _angle, typeof(RadianAngle));
        }

        /// <summary>
        /// Implements the == operator for the <see cref="AxisAngle"/> type.
        /// </summary>
        /// <param name="x">The first operand.</param>
        /// <param name="y">The second operand.</param>
        /// <returns><c>true</c> if the two operands are considered equal; else <c>false</c>.</returns>
        public static Boolean operator ==(AxisAngle x, AxisAngle y)
        {
            return x.Equals(y);
        }

        /// <summary>
        /// Implements the != operator for the <see cref="AxisAngle"/> type.
        /// </summary>
        /// <param name="x">The first operand.</param>
        /// <param name="y">The second operand.</param>
        /// <returns><c>true</c> if the two operands are not considered equal; else <c>false</c>.</returns>
        public static Boolean operator !=(AxisAngle x, AxisAngle y)
        {
            return !x.Equals(y);
        }
    }
}